// C++ Templates: 第五章 基本技巧
// 5.1 关键字typename
// 在模板中，typename关键字有两个主要用途：
// 1.声明模板类型参数（与class关键字可互换）
// 2.指明依赖名称是类型（这是必须的）
// 依赖名称问题
// 当在模板中使用嵌套依赖类型时，必须使用typename关键字：

template <typename T>
void func()
{
    // 错误：编译器不知道T::iterator是类型还是静态成员
    T::iterator it;

    // 正确：明确指出T::iterator是类型
    typename T::iterator it;
}

// 何时需要typename
// 只有当类型名称依赖于模板参数时才需要typename：
template <typename T>
void func()
{
    typename T::iterator it1;       // 需要typename，因为依赖于T
    std::vector<int>::iterator it2; // 不需要typename，因为不依赖于模板参数
}

// 5.2 零初始化
// 模板中处理内置类型和用户定义类型时，初始化是一个常见问题。
// 问题
template <typename T>
void func()
{
    T x; // 对于内置类型，x的值是未定义的
}
// 解决方案
template <typename T>
void func()
{
    T x{}; // 零初始化：内置类型初始化为0，类对象调用默认构造函数
}
// 零初始化的方法
// 不同的零初始化方式
T x{};     // C++11起推荐，统一初始化语法
T x = T(); // 传统方式
T x = {};  // C++11起可用

// 5.3 使用this->
// 在派生类模板中，访问基类的成员时可能需要使用this->前缀。
// 问题
template <typename T>
class Base
{
public:
    void func() {}
};

template <typename T>
class Derived : public Base<T>
{
public:
    void method()
    {
        func(); // 错误：编译器不知道func是从Base<T>继承的
        // 这个问题的原因是"依赖名称查找"规则：编译器在解析模板时，不会在依赖基类中查找非依赖名称。
    }
};
// 解决方案
template <typename T>
class Derived : public Base<T>
{
public:
    void method()
    {
        this->func(); // 正确：明确指出func是继承的成员
        // 或者
        Base<T>::func(); // 也正确
    }
};

// 5.4 处理原始数组和字符串字面量的模板
// 模板参数推导不会保留数组和引用的维度信息，这可能导致问题。
// 数组退化
template <typename T>
void func(T param)
{
    // 当传入数组时，T被推导为指针类型
}

template <typename T, std::size_t N>
void arrayFunc(T (&arr)[N])
{
    // 保留数组维度信息
    std::cout << "Array size: " << N << std::endl;
}

// 字符串字面量
template <typename T, std::size_t N>
void stringFunc(T (&str)[N])
{
    // 数组引用语法解析
    // T    -数组元素的类型
    // &arr -名为arr的引用变量
    // [N]  -表示这是一个包含N个元素的数组
    // 整体 T (&arr)[N] -对一个包含N个T类型元素的数组的引用

    // 适用于字符串字面量
    std::cout << "String length: " << N - 1 << std::endl; // 减1是因为结尾的'\0'
}

// 使用
stringFunc("hello"); // 输出：String length: 5

// 5.5 成员模板
// 类和类模板可以有模板成员函数，这些称为成员模板。
// 基本用法
class String
{
public:
    // 成员模板
    template <typename T>
    String(const T &x)
    {
        // 从任何类型构造String
    }
};

template <typename T>
class Container
{
public:
    // 类模板中的成员模板
    template <typename U>
    void insert(const U &element)
    {
        // 插入任何类型的元素
    }
};

// 5.5.1 .template构造
// 当在依赖名称上调用成员模板时，需要使用.template语法：
template <typename T>
class MyClass
{
public:
    template <typename U>
    void func() {}
};

template <typename T>
void example(MyClass<T> &obj)
{
    // 错误：编译器不知道<是模板参数列表的开始还是小于运算符
    obj.func<int>();

    // 正确：明确指出这是模板调用
    obj.template func<int>();
}

// 5.5.2 泛型lambda和成员模板
// C++14引入的泛型lambda实际上是成员模板的一种应
// C++14泛型lambda
auto lambda = [](auto x)
{
    return x * 2;
};

// 等价于
struct
{
    template <typename T>
    auto operator()(T x) const
    {
        return x * 2;
    }
} lambda;

// 5.6 变量模板
// C++14引入了变量模板，允许我们定义依赖于模板参数的变量。
// 变量模板
template <typename T>
constexpr T pi = T(3.1415926535897932385);

// 使用
double area = pi<double> * radius * radius;
float area_f = pi<float> * radius * radius;

// 应用：简化类型特征
// 传统方式
std::is_same<int, int>::value

// 使用变量模板(C++17)
std::is_same_v<int, int>

// 5.7 模板的模板参数
// 模板参数本身也可以是模板，这称为模板的模板参数。
// 基本语法
// 模板的模板参数
template<template<typename> class Container>
class Stack {
private:
    Container<int> elems;  // 使用Container作为模板
    
public:
    void push(int elem) {
        elems.push_back(elem);
    }
};

// 使用
Stack<std::vector> intStack;  // 使用std::vector作为容器

// 模板参数匹配问题
// 模板的模板参数必须精确匹配，包括默认模板参数：
// 这不会工作，因为std::vector有额外的模板参数
template<template<typename> class Container>
class Stack { /*...*/ };

Stack<std::vector> s;  // 错误：std::vector有两个模板参数

// 正确的方式
template<template<typename, typename = std::allocator<typename>> class Container>
class Stack { /*...*/ };

Stack<std::vector> s;  // 现在可以工作


// 5.8 小结
// 第五章介绍了C++模板编程中的一些基本技巧：
// 1.关键字typename：用于指明依赖名称是类型
// 2.零初始化：确保模板中的变量正确初始化
// 3.使用this->：在派生类模板中访问基类成员
// 4.处理数组和字符串：保留数组维度信息
// 5.成员模板：类中的模板成员函数
// 6.变量模板：依赖于模板参数的变量
// 7.模板的模板参数：以模板作为模板参数